Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Staking #52

Merged
merged 65 commits into from
Dec 7, 2023
Merged

Staking #52

merged 65 commits into from
Dec 7, 2023

Conversation

r-czajkowski
Copy link
Contributor

@r-czajkowski r-czajkowski commented Nov 27, 2023

Closes: #25

This PR adds the initial implementation of the stake function. A staker should call stake function to stake ERC20 token (tBTC) in the main Acre contract. In return the receiver will receive ERC4626 reward-bearing token (stBTC). The function should accept referral input parameter that will be later used for accounting (out of scope of this issue).

We want to implement the ERC4626 tokenized vault standard based on
OpenZeppelin contracts.
Based on [the solhint docs](https://github.com/protofire/solhint/blob/develop/docs/rules/security/func-visibility.md)
This rule is required to be true for Solidity `>=0.7.0`.
The Acre contract is ERC4626 tokenized vault standard. Here we inherit
from abstract contract `ERC4626` from OpenZeppelin lib. We are going to
implement some custom features in follow-up work but the core is based
on the ERC4626 standard.
Stakes a given amount of underlying token and mints shares to a
receiver. This function calls `deposit` function from `ERC4626`
contract. The function should accept `referrer` input parameter that will
be later used for accounting (out of scope of this issue).
We want to support `receiveApproval`/`approveAndCall` pattern in the
Acre contract. This package provides required interfaces to support this
pattern.
The tBTC token contract that will be a staking token supports this
pattern. To be able to stake in one transaction (instead of 2: approve +
stake) we must implement the `RecieveApproval` interface. The token
staking contract receives approval to spend tokens and create a stake
for a given account.
Use this contract in unit tests as tBTC token.
@r-czajkowski r-czajkowski requested review from dimpar and nkuba November 27, 2023 15:56
@r-czajkowski r-czajkowski self-assigned this Nov 27, 2023
@r-czajkowski r-czajkowski added the 🔗 Solidity Solidity contracts label Nov 27, 2023
@r-czajkowski r-czajkowski added this to the MVP milestone Nov 27, 2023
Copy link
Member

@dimpar dimpar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First round

core/contracts/Acre.sol Outdated Show resolved Hide resolved
core/contracts/Acre.sol Outdated Show resolved Hide resolved
core/contracts/Acre.sol Outdated Show resolved Hide resolved
core/contracts/Acre.sol Outdated Show resolved Hide resolved
core/contracts/Acre.sol Outdated Show resolved Hide resolved
core/contracts/Acre.sol Show resolved Hide resolved
core/contracts/Acre.sol Outdated Show resolved Hide resolved
core/contracts/Acre.sol Show resolved Hide resolved
core/contracts/test/TestToken.sol Outdated Show resolved Hide resolved
core/test/Acre.test.ts Outdated Show resolved Hide resolved
core/contracts/Acre.sol Outdated Show resolved Hide resolved
core/contracts/Acre.sol Outdated Show resolved Hide resolved
There is no need to add `super` keyword unless you want to override to
function and call the original implementation.
We decided to use `Acre Staked Bitcoin` as a full name of the ERC4626
token.
Use `token` as it's how it is defined in the `IReceiveApproval`
interface. It also doesn't collide with another property so the
underscore is unnecessary.
This function is called by `receiveApproval` internally so we should use
`public` instead of `external`.
Rename `token` -> `tbtc`.
`referrer` -> `referral`
We've updated the revert message in `stake` function when validating the
`referral` param. Here we adjust the revert message in unit tests to the
current version of contract.
`valu` -> `value`
Unit test stakig flow where there are more than one staker and the vault
earns yield from strategies.
Instead of this require we want to emit an event that will include the
referral. The off-chain accounting will need it to calculate the fee
shares. To not duplicate the `Deposit` event from `ERC4626` the `Staked`
event has only context for referral program. We can get other values
from the `Deposit` event since it will be emitted in the same
transaction.
@r-czajkowski r-czajkowski marked this pull request as ready for review November 30, 2023 09:55
This reverts commit f36c65c.

To support receive approval pattern we need to override the
`ERC4626.deposit` function to allow pass an `owner`. The OZ
implementation sets the owner as `msg.sender` by default under the hood
see
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC4626.sol#L178
and we can't change the owner. W/o this update using approve and call
pattern to stake tokens, the `msg.sender` is tBTC token address, so we
are actually trying to send tokens from the token contract to the
receiver, which is impossible. We are going to address it in a separate
PR.
`Token` -> `TestERC20` to be consistent.
Let's deploy the contracts first and then perform the minting.
It's worth to match the test name with the function name.
`TestToken` -> `TestERC20`. The file name should match the contract
name.
To avoid a false-positive result where both contracts and tests
calculations are wrong.
We validate the exact value in the next line so this check seems
redundant.
`when staking` -> `when staking as a first staker`.
Define a variable `amountToStake` instead of passing number directly to
functions - to improve readability.
Since the variable is named `amountToStake`, the amount we pass to
`stake` function should be exactly `amountToStake`. Here we add a new
variable `approvedAmount` which will be approved in the `before` hook
and we use this value when defining the `amountToStake` which is equal
`amountToStake + 1`. This will imporve readability.
We should use `expectedReceivedShares` while checking `stBTC` balance
after staking.
Since the ERC4626 deposit function has different caller and receiver
roles, let's test them here. This will reflect the flow for tBTC minting
and staking in one transaction, where an external contract will call the
stake function in the name of the staker.
@r-czajkowski r-czajkowski requested a review from nkuba December 5, 2023 17:05
Complicate the math by using `50` instead of `100` to use a different
value than `100` which we already have in `totalShares` (25+75=100).
core/test/Acre.test.ts Outdated Show resolved Hide resolved
core/test/Acre.test.ts Outdated Show resolved Hide resolved
core/test/Acre.test.ts Show resolved Hide resolved
Add short description what is the purpose of Acre system.
The equality check is enough given we hardcode the expected values.
If the call reverts it will throw an exception in  `beforeEach` hook for
`await acre.connect(staker1).stake(...)`.
@r-czajkowski r-czajkowski requested a review from nkuba December 6, 2023 12:52
Copy link
Member

@dimpar dimpar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no more comments. Leaving the final approval for Kuba.

Copy link
Member

@nkuba nkuba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left non-blocking comments.

})

context(
"when a staker approved and staked tokens and wants to stake more but w/o another apporval",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"when a staker approved and staked tokens and wants to stake more but w/o another apporval",
"when a staker approved and staked tokens and wants to stake more but w/o another approval",

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

)
})

context("when there are two stakers, A and B ", () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
context("when there are two stakers, A and B ", () => {
context("when there are two stakers", () => {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +242 to +254
it("should stake tokens correctly", async () => {
await expect(
acre
.connect(staker1)
.stake(staker1AmountToStake, staker1.address, referral),
).to.be.not.reverted
})

it("should receive shares equal to a staked amount", async () => {
const shares = await acre.balanceOf(staker1.address)

expect(shares).to.eq(staker1AmountToStake)
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could simplify this test:

Suggested change
it("should stake tokens correctly", async () => {
await expect(
acre
.connect(staker1)
.stake(staker1AmountToStake, staker1.address, referral),
).to.be.not.reverted
})
it("should receive shares equal to a staked amount", async () => {
const shares = await acre.balanceOf(staker1.address)
expect(shares).to.eq(staker1AmountToStake)
})
it("should receive shares equal to a staked amount", async () => {
const tx = acre
.connect(staker1)
.stake(staker1AmountToStake, staker1.address, referral)
await expect(tx).to.changeTokenBalances(
acre,
[staker1.address],
[staker1AmountToStake],
)
})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nkuba nkuba merged commit 85d9524 into main Dec 7, 2023
9 checks passed
@nkuba nkuba deleted the token-vault branch December 7, 2023 10:04
dimpar added a commit that referenced this pull request Dec 7, 2023
Depends on #52

Acre contract requires tBTC address as argument.
We add `00_resolve_tbtc.ts` script that will ensure tBTC deployment
artifact reference is available based on the network.
For networks marked with `allowStubs` tag (hardhat local network for
running tests) a stub ERC20 contract will be deployed.

Added a script `./scripts/fetch_external_artifacts.sh` that downloads
TBTC token deployment artifact from NPM packages for sepolia and mainnet
network and places them under `./external` directory where they will be
used for contracts deployment.
@nkuba nkuba mentioned this pull request Jan 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🔗 Solidity Solidity contracts
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement Staking
4 participants